package com.piggy.logi.sys.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.collect.Lists;
import com.piggy.common.core.constant.SecurityConstants;
import com.piggy.common.core.enums.CacheKeyEnums;
import com.piggy.common.core.exception.GlobalException;
import com.piggy.common.core.exception.ServiceException;
import com.piggy.common.core.exception.base.BaseException;
import com.piggy.common.core.utils.PageUtils;
import com.piggy.common.core.utils.sql.SqlUtil;
import com.piggy.common.core.web.page.PagePlus;
import com.piggy.common.core.web.page.TableDataInfo;
import com.piggy.common.redis.cache.CacheUtils;
import com.piggy.common.satoken.utils.SecurityUtils;
import com.piggy.logi.sys.bo.*;
import com.piggy.logi.sys.domain.LogiGoodOrder;
import com.piggy.logi.sys.domain.LogiOrderGoodSource;
import com.piggy.logi.sys.domain.LogiPricePlan;
import com.piggy.logi.sys.domain.LogiWaybill;
import com.piggy.logi.sys.enums.FeeTypeEnum;
import com.piggy.logi.sys.enums.GoodOrderStatusEnum;
import com.piggy.logi.sys.enums.WaybillStatusEnum;
import com.piggy.logi.sys.mapper.LogiGoodOrderMapper;
import com.piggy.logi.sys.service.*;
import com.piggy.logi.sys.vo.LogiCustomerExtVo;
import com.piggy.logi.sys.vo.LogiGoodOrderExtVo;
import com.piggy.logi.sys.vo.LogiGoodOrderVo;
import com.piggy.logi.sys.vo.LogiOrderGoodSourceExtVo;
import com.piggy.sys.api.RemoteDistrictService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;

import static com.piggy.logi.sys.api.service.impl.ZhongYouApiServiceImpl.getMilesByAddress;

@Service
public class LogiGoodOrderServiceImpl extends ServiceImpl<LogiGoodOrderMapper, LogiGoodOrder> implements ILogiGoodOrderService {

    @Resource
    private RemoteDistrictService remoteDistrictService;
    @Resource
    private ILogiGoodSourceService logiGoodSourceService;
    @Resource
    private ILogiOrderGoodSourceService logiOrderGoodSourceService;
    @Resource
    private ILogiCustomerService customerService;
    @Resource
    private ILogiSysBaseConfigService baseConfigService;
    @Resource
    private ILogiWaybillService waybillService;
    @Resource
    private ILogiPricePlanService pricePlanService;
    @Resource
    private ILogiPriceSegService priceSegService;

    @Override
    public LogiGoodOrderExtVo queryById(Long id) {
        LogiGoodOrderExtVo extVo = getVoById(id, LogiGoodOrderExtVo.class);
        if (extVo == null) {
            return null;
        }
        return fillData(Lists.newArrayList(extVo)).get(0);
    }

    @Override
    public TableDataInfo<LogiGoodOrderExtVo> queryPageList(LogiGoodOrderQueryBo bo) {
        PagePlus<LogiGoodOrder, LogiGoodOrderExtVo> result = pageVo(PageUtils.buildPagePlus(bo, true), buildQueryWrapper(bo), LogiGoodOrderExtVo.class);
        List<LogiGoodOrderExtVo> extVoList = result.getRecordsVo();
        fillData(extVoList);
        return PageUtils.buildDataInfo(result);
    }

    @Override
    public List<LogiGoodOrderExtVo> queryList(LogiGoodOrderQueryBo bo) {
        List<LogiGoodOrderExtVo> extVoList = listVo(buildQueryWrapper(bo), LogiGoodOrderExtVo.class);
        fillData(extVoList);
        return extVoList;
    }

    private List<LogiGoodOrderExtVo> fillData(List<LogiGoodOrderExtVo> extVoList) {
        if (CollUtil.isEmpty(extVoList)) {
            return extVoList;
        }
        // 查询价格方案
        LogiPricePlan pricePlan = pricePlanService.getOne(Wrappers.lambdaQuery(LogiPricePlan.class)
                .eq(LogiPricePlan::getProjectId, 0)
                .eq(LogiPricePlan::getProjectShiplineId, 0)
                .eq(LogiPricePlan::getDeptId, SecurityUtils.getMainDeptId())
                .eq(LogiPricePlan::getFeeMode, FeeTypeEnum.WEIGHTMULTIMILEAGE.getType())
                .last("limit 1"));
        if (ObjectUtil.isNull(pricePlan)) {
            throw new ServiceException("未配置【重量*里程】价格方案");
        }
        // 获取地址数据
        List<String> distractIds = Lists.newArrayList();
        extVoList.forEach(extVo -> {
            if (extVo.getOriginDistractId() != null) {
                distractIds.add(extVo.getOriginDistractId().toString());
            }
            if (extVo.getDestinationDistractId() != null) {
                distractIds.add(extVo.getDestinationDistractId().toString());
            }
        });
        Map<String, String> districtNameMap = remoteDistrictService.getDistrictMap(distractIds, SecurityConstants.INNER).getData();

        // 获取关联运单信息
        List<Long> waybillIds = extVoList.stream().map(LogiGoodOrderExtVo::getWaybillId).filter(ObjectUtil::isNotNull).distinct().collect(Collectors.toList());
        Map<Long, LogiWaybill> waybillMap;
        if (CollUtil.isNotEmpty(waybillIds)) {
            waybillMap = waybillService.listByIds(waybillIds).stream().collect(Collectors.toMap(LogiWaybill::getWaybillId, Function.identity()));
        } else {
            waybillMap = MapUtil.newHashMap();
        }

        // 获取货源数据
        List<Long> goodOrderIds = extVoList.stream().map(LogiGoodOrderExtVo::getGoodOrderId).collect(Collectors.toList());
        List<LogiOrderGoodSource> orderGoodSourceList = logiOrderGoodSourceService.getByGoodOrderIds(goodOrderIds);
        Map<Long, List<LogiOrderGoodSource>> goodSourceExtVoMap = orderGoodSourceList.stream().collect(Collectors.groupingBy(LogiOrderGoodSource::getGoodOrderId));

        for (LogiGoodOrderExtVo extVo : extVoList) {
            Optional.ofNullable(extVo.getOriginDistractId()).ifPresent(item -> extVo.setOriginDistractName(districtNameMap.get(item.toString())));
            Optional.ofNullable(extVo.getDestinationDistractId()).ifPresent(item -> extVo.setDestinationDistractName(districtNameMap.get(item.toString())));

            extVo.setGoodSourceList(BeanUtil.copyToList(goodSourceExtVoMap.get(extVo.getGoodOrderId()), LogiOrderGoodSourceExtVo.class));

            if (ObjectUtil.isNotNull(extVo.getWaybillId())) {
                if (waybillMap.containsKey(extVo.getWaybillId())) {
                    extVo.setOutCode(waybillMap.get(extVo.getWaybillId()).getOutCode());
                    extVo.setScheduleNo(waybillMap.get(extVo.getWaybillId()).getScheduleNo());
                    extVo.setWaybillStatus(waybillMap.get(extVo.getWaybillId()).getStatus());
                } else {
                    extVo.setWaybillStatus(WaybillStatusEnum.STATUS_UNKNOWN.getCode());
                }
            }

            // 根节点母单统计运费信息
            if (extVo.getParentId() == 0) {
                // 原订单运费=下面运单运费的和（相当于是对子订单关联运单运费总价累计，做个统计）
                List<LogiGoodOrder> goodOrderList = list(Wrappers.lambdaQuery(LogiGoodOrder.class)
                        .eq(LogiGoodOrder::getMainParentId, extVo.getGoodOrderId()));
                waybillIds = goodOrderList.stream().filter(a -> ObjectUtil.isNotNull(a.getWaybillId())).map(LogiGoodOrder::getWaybillId).collect(Collectors.toList());
                if (CollUtil.isNotEmpty(waybillIds)) {
                    BigDecimal orderFee = waybillService.listByIds(waybillIds).stream().map(LogiWaybill::getTotalCarriageFee).reduce(BigDecimal.ZERO, BigDecimal::add);
                    extVo.setOrderFee(orderFee);
                } else {
                    extVo.setOrderFee(BigDecimal.ZERO);
                }
                // 实际订单运费=根据客户传过来货物重量+计费公式自动计算
                BigDecimal realOrderFee = BigDecimal.ZERO;
                if (CollUtil.isNotEmpty(extVo.getGoodSourceList())) {
                    for (LogiOrderGoodSourceExtVo goodSourceExtVo : extVo.getGoodSourceList()) {
                        if (StrUtil.isNotBlank(goodSourceExtVo.getRealWeight())) {
                            // 根据收发货地获取里程数
                            BigDecimal miles = getMilesByAddress(extVo.getOriginAddress(), extVo.getDestinationAddress());
                            // 预算运费
                            realOrderFee = realOrderFee.add(priceSegService.calcWeigthMultiMileFee(pricePlan.getId(), new BigDecimal(goodSourceExtVo.getRealWeight()), miles));
                        }
                    }
                }
                extVo.setRealOrderFee(realOrderFee);
            }
        }
        List<Long> cusIds = extVoList.stream().map(LogiGoodOrderVo::getShipId).distinct().collect(Collectors.toList());
        cusIds.addAll(extVoList.stream().map(LogiGoodOrderVo::getDeliveryId).distinct().collect(Collectors.toList()));
        if (CollUtil.isEmpty(cusIds)) {
            cusIds = Lists.newArrayList(0L);
        }
        List<LogiCustomerExtVo> cusList = customerService.queryList(new LogiCustomerQueryBo().setCustomerIdList(cusIds));
        if (CollUtil.isNotEmpty(cusList)) {
            Map<Long, LogiCustomerExtVo> cusMap = cusList.stream().collect(Collectors.toMap(LogiCustomerExtVo::getCustomerId, Function.identity(), (a, b) -> a));
            for (LogiGoodOrderExtVo extVo : extVoList) {
                Optional.ofNullable(extVo.getShipId()).ifPresent(item -> extVo.setShipName(cusMap.get(item).getCustomerName()));
                Optional.ofNullable(extVo.getDeliveryId()).ifPresent(item -> extVo.setDeliveryName(cusMap.get(item).getCustomerName()));
            }
        }


        return extVoList;
    }

    private LambdaQueryWrapper<LogiGoodOrder> buildQueryWrapper(LogiGoodOrderQueryBo bo) {
        LambdaQueryWrapper<LogiGoodOrder> lqw = Wrappers.lambdaQuery();
        //lqw.eq(LogiGoodOrder::getDeptId, bo.getDeptId());
        lqw.like(StrUtil.isNotBlank(bo.getOrderCode()), LogiGoodOrder::getOrderCode, bo.getOrderCode());
        lqw.like(ObjectUtil.isNotNull(bo.getGoodOrderType()), LogiGoodOrder::getGoodOrderType, bo.getGoodOrderType());
        lqw.like(ObjectUtil.isNotNull(bo.getStatus()), LogiGoodOrder::getStatus, bo.getStatus());
        lqw.eq(StrUtil.isNotBlank(bo.getOriginDistractId()), LogiGoodOrder::getOriginDistractId, bo.getOriginDistractId());
        lqw.eq(StrUtil.isNotBlank(bo.getDestinationDistractId()), LogiGoodOrder::getDestinationDistractId, bo.getDestinationDistractId());
        lqw.like(StrUtil.isNotBlank(bo.getOwnerOutCode()), LogiGoodOrder::getOwnerOutCode, bo.getOwnerOutCode());
        lqw.like(StrUtil.isNotBlank(bo.getPickupOrderNo()), LogiGoodOrder::getPickupOrderNo, bo.getPickupOrderNo());
        //lqw.eq(bo.getDeptId() != null, LogiGoodOrder::getDeptId, bo.getDeptId());
        lqw.eq(bo.getShipId() != null, LogiGoodOrder::getShipId, bo.getShipId());
        lqw.eq(bo.getDeliveryId() != null, LogiGoodOrder::getDeliveryId, bo.getDeliveryId());
        if (CollUtil.isNotEmpty(bo.getCreateTimeStr())) {
            lqw.apply("DATE_FORMAT(create_time, '%Y-%m-%d') >= {0}", bo.getCreateTimeStr().get(0));
            lqw.apply("DATE_FORMAT(create_time, '%Y-%m-%d') <= {0}", bo.getCreateTimeStr().get(1));
        }
        lqw.apply("good_order_id not in (select distinct parent_id from logi_good_order)");
        if (ObjectUtil.isNotNull(bo.getQueryType())) {
            if (bo.getQueryType() == 1) {
                lqw.isNull(LogiGoodOrder::getWaybillId);
            } else if (bo.getQueryType() == 2) {
                lqw.isNotNull(LogiGoodOrder::getWaybillId);
            }
        }
        // 部门数据权限过滤
        if (ObjectUtil.isNotNull(bo.getDeptId())) {
            SqlUtil.dataPermissionFilter(lqw, bo.getDeptId());
        }
        return lqw;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean insertByAddBo(LogiGoodOrderEditExtBo bo) {
        LogiGoodOrder add = BeanUtil.toBean(bo, LogiGoodOrder.class);

        if (StrUtil.isBlank(bo.getOrderCode())) {
            add.setOrderCode(genOrderCode(bo.getDeptId()));
        }

        add.setStatus(GoodOrderStatusEnum.NORMAL.getCode());
        //add.setGoodOrderType(0);

        validEntityBeforeSave(add);
        save(add);
        List<LogiOrderGoodSourceEditBo> orderGoodSourceList = bo.getGoodSourceList();
        if (CollUtil.isEmpty(orderGoodSourceList)) {
            return Boolean.TRUE;
        }
        orderGoodSourceList.forEach(item -> {
            item.setGoodOrderId(add.getGoodOrderId());
            item.setDeptId(add.getDeptId());
        });
        logiOrderGoodSourceService.saveBatch(BeanUtil.copyToList(orderGoodSourceList, LogiOrderGoodSource.class));
        return Boolean.TRUE;
    }

    @Override
    public Boolean updateByEditBo(LogiGoodOrderEditExtBo bo) {
        LogiGoodOrder update = BeanUtil.toBean(bo, LogiGoodOrder.class);
        if (update.getGoodOrderType() == 1) {
            throw new BaseException("该订单是线上订单，不允许编辑");
        }
        validEntityBeforeSave(update);
        updateById(update);
        List<LogiOrderGoodSourceEditBo> orderGoodSourceList = bo.getGoodSourceList();
        if (CollUtil.isNotEmpty(orderGoodSourceList)) {
            orderGoodSourceList.forEach(item -> {
                item.setGoodOrderId(update.getGoodOrderId());
                item.setDeptId(update.getDeptId());
            });
        }
        logiOrderGoodSourceService.batchUpdateByGoodOrderId(update.getGoodOrderId(), BeanUtil.copyToList(orderGoodSourceList, LogiOrderGoodSource.class));
        return Boolean.TRUE;
    }

    /**
     * A、对同一个厂家、同一收发货方、收发货地址订单进行拆分。
     * B、拆分需要面向订单不同货物进行拆分。
     * C、同一货源单号拆分后面加-，根据拆分数量拆分-加数号。
     * D、拆分后的子订单可继续拆分，再次拆分后，后面继续加-数号
     * E、拆分后的子订单部分信息不可更新，需灰显（发货人、收货方、项目、货主单号、发货地、目的地、货物信息、订单附件）
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean split(LogiGoodOrderSplitBo bo) {
        LogiGoodOrder goodOrder = getById(bo.getGoodOrderId());
        Long mainParentId = goodOrder.getParentId() == 0 ? goodOrder.getGoodOrderId() : goodOrder.getMainParentId();
        Long parentId = goodOrder.getGoodOrderId();
        String parentOrderCode = goodOrder.getOrderCode();

        List<LogiGoodOrderEditExtBo> splitBos = bo.getGoodOrderEditExtBos();
        for (int i = 0; i < splitBos.size(); i++) {
            int index = i + 1;
            LogiGoodOrderEditExtBo insertBo = splitBos.get(i);
            insertBo.setMainParentId(mainParentId);
            insertBo.setParentId(parentId);
            insertBo.setOrderCode(parentOrderCode + "-" + index);
            insertByAddBo(insertBo);
        }
        // TODO logic delete
        return Boolean.TRUE;
    }

    /**
     * A、选择所有二级子单号合并为原母单号。
     * B、部分二级子单号合并按照拆分单号排在最后单号，作为新单号整合其它单。
     * C、二级之后的子订单同样全部可合并为原二级订单，部分子订单合并按照拆分单号排在最后的单号，作为新单号。
     * D、必须在列表中选择两个及两个以上同级子订单进行合并，合并之后物品及物品数量进行累加。
     * E、合并若其它信息有变动，按照最新订单更新信息同步成新的合并订单内容。
     * (其它信息包含：发车时间、到达时间、提货时间、送货时间、发货网点、紧急程度、发货提货方式、到达提货方式、相关服务、备注)
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean merge(LogiGoodOrderMergeBo bo) {
        LogiGoodOrder parentGoodOrder = getById(bo.getParentId());
        List<LogiGoodOrder> goodOrders = baseMapper.selectByParentOrderCode(bo.getParentId());
        List<String> goodOrderCodes = bo.getGoodOrderCodes();
        if (CollUtil.isEmpty(goodOrders) || parentGoodOrder == null) {
            throw new GlobalException("母单ID异常");
        }
        List<String> allGoodOrderCodes = goodOrders.stream().map(LogiGoodOrder::getOrderCode).collect(Collectors.toList());
        List<String> afterMergeCodes = allGoodOrderCodes.stream().filter(element -> !goodOrderCodes.contains(element)).collect(Collectors.toList());
        boolean isAllMerge = CollUtil.isEmpty(afterMergeCodes);

        if (isAllMerge) {
            // 全部合并
            LogiGoodOrderEditExtBo newGoodOrder = BeanUtil.toBean(goodOrders.get(0), LogiGoodOrderEditExtBo.class);
            newGoodOrder.setGoodSourceList(mergeGoodSourceList(goodOrders, goodOrderCodes));
            updateByEditBo(newGoodOrder);
        } else {
            // 部分合并
            String[] lastGoodCodeSplit = allGoodOrderCodes.get(allGoodOrderCodes.size() - 1).split("-");
            String goodCode = parentGoodOrder.getOrderCode() + "-" + (Integer.parseInt(lastGoodCodeSplit[lastGoodCodeSplit.length - 1]) + 1);
            // 合并货物
            LogiGoodOrderEditExtBo newGoodOrder = BeanUtil.toBean(goodOrders.get(0), LogiGoodOrderEditExtBo.class);
            newGoodOrder.setGoodSourceList(mergeGoodSourceList(goodOrders, goodOrderCodes));
            newGoodOrder.setOrderCode(goodCode);
            newGoodOrder.setGoodOrderId(null);
            insertByAddBo(newGoodOrder);
        }
        this.remove(new LambdaQueryWrapper<LogiGoodOrder>().in(LogiGoodOrder::getOrderCode, bo.getGoodOrderCodes()));
        return false;
    }

    private List<LogiOrderGoodSourceEditBo> mergeGoodSourceList(List<LogiGoodOrder> goodOrders, List<String> goodOrderCodes) {
        List<LogiOrderGoodSourceEditBo> orderGoodSourceList = Lists.newArrayList();
        List<Long> goodOrderIds = goodOrders.stream().filter(item -> goodOrderCodes.contains(item.getOrderCode()))
                .map(LogiGoodOrder::getGoodOrderId).collect(Collectors.toList());
        List<LogiOrderGoodSource> orderOrderGoodSourceList = logiOrderGoodSourceService.getByGoodOrderIds(goodOrderIds);
        Map<String, List<LogiOrderGoodSource>> stringListMap = orderOrderGoodSourceList.stream().collect(Collectors.groupingBy(LogiOrderGoodSource::getGoodsName));
        stringListMap.forEach((goodName, list) -> {
            LogiOrderGoodSourceEditBo editBo = BeanUtil.toBean(list.get(0), LogiOrderGoodSourceEditBo.class);
            if (list.size() > 1) {
                for (int i = 1; i < list.size(); i++) {
                    LogiOrderGoodSource temp = list.get(i);
                    editBo.setNum(Integer.parseInt(StrUtil.blankToDefault(editBo.getNum(), "0")) + Integer.parseInt(StrUtil.blankToDefault(temp.getNum(), "0")) + "");
                    editBo.setWeight(Integer.parseInt(StrUtil.blankToDefault(editBo.getWeight(), "0")) + Integer.parseInt(StrUtil.blankToDefault(temp.getWeight(), "0")) + "");
                    editBo.setBulk(Integer.parseInt(StrUtil.blankToDefault(editBo.getBulk(), "0")) + Integer.parseInt(StrUtil.blankToDefault(temp.getBulk(), "0")) + "");
                }
            }
            editBo.setId(null);
            orderGoodSourceList.add(editBo);
        });
        return orderGoodSourceList;
    }

    /**
     * 保存前的数据校验
     *
     * @param entity 实体类数据
     */
    private void validEntityBeforeSave(LogiGoodOrder entity) {
        //TODO 做一些数据校验,如唯一约束
    }

    @Override
    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
        if (isValid) {
            // 做一些业务上的校验,判断是否需要校验
            List<LogiGoodOrder> logiGoodOrders = listByIds(ids);
            List<String> onlineOrders = CollUtil.newArrayList();
            for (LogiGoodOrder logiGoodOrder : logiGoodOrders) {
                if (logiGoodOrder.getGoodOrderType() == 1) {
                    onlineOrders.add(logiGoodOrder.getOrderCode());
                }
            }
            if (!onlineOrders.isEmpty()) {
                throw new BaseException("订单号【" + String.join(",", onlineOrders) + "】是线上订单，不允许删除");
            }
        }
        return removeByIds(ids);
    }

    @Override
    public String genOrderCode(Long deptId) {
        String orderPrefix = baseConfigService.getGoodOrderNo(deptId);
        String key = CacheKeyEnums.LogiGoodOrderNo.getKey(orderPrefix);
        if (!CacheUtils.hasKey(key)) {
            CacheUtils.cache(0, key, 2L, TimeUnit.DAYS);
        }

        String orderCode = null;
        for (int i = 0; i < 3; i++) {
            Long no = CacheUtils.incrBy(key);
            orderCode = String.format("%s%06d", orderPrefix, no);
            if (count(Wrappers.lambdaQuery(LogiGoodOrder.class).eq(LogiGoodOrder::getOrderCode, orderCode)) > 0) {
                orderCode = null;
            } else {
                break;
            }
        }
        if (StrUtil.isBlank(orderCode)) {
            throw new GlobalException("订单号生成失败，请稍后重试");
        }
        return orderCode;
    }
}
